#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/input.h>
#include <stdlib.h>
#include <time.h>

// 全局变量的定义
int fd = 0; // 屏幕的文件描述符
int *plcd = NULL; // 屏幕映射后的地址


// 初始化屏幕
void lcd_init();
// 关闭屏幕
void lcd_close();
// 可以在屏幕上指定的位置显示一个指定颜色的点
void lcd_draw_point(int x, int y, int color);
// 刷屏，可以把屏幕刷成指定的颜色
void lcd_clear(int color);
// 在指定的位置，显示一个指定大小，指定颜色的矩形
void lcd_draw_rect(int x0, int y0, int w, int h, int color);
// 能做把指定的图片显示到指定的坐标(x0,y0)上
void lcd_draw_bmp(char *name, int x0, int y0);



// 触摸屏操作函数
// 返回值表示点击了不同的位置/结果
int get_touch();


// 内部的实现其实是一个 4*4 的二维数组
int num[4][4] = {0}; // 2048 小游戏内部的数组

// 往数组中随机空白的位置添加一个随机的 2/4
void add_num()
{
    int i, j;
    int x, y;
    int blank_count = 0;
    int blank_pos[16][2]; // 存储空白位置的数组，最多 16 个（4x4 矩阵最多 16 个空白）

    // 找出所有的空白位置
    for (j = 0; j < 4; j++) {
        for (i = 0; i < 4; i++) {
            if (num[j][i] == 0) {
                blank_pos[blank_count][0] = i;
                blank_pos[blank_count][1] = j;
                blank_count++;
            }
        }
    }

    if (blank_count > 0) {
        // 从空白位置中随机选择一个
        int index = rand() % blank_count;
        x = blank_pos[index][0];
        y = blank_pos[index][1];
        // 随机生成 2 或 4，生成 4 的概率为 10%
        num[y][x] = (rand() % 10 == 0)? 4 : 2;
    }
}

// 把当前的数组"画"到屏幕上
void draw_2048()
{
    int i, j;
    // 打印到终端
    for (j = 0; j < 4; j++) {
        for (i = 0; i < 4; i++) {
            printf("%d\t", num[j][i]);
        }
        printf("\n");
    }

    // 显示到 LCD 屏幕上
    for (j = 0; j < 4; j++) {
        for (i = 0; i < 4; i++) {
            // 需要把 num[j][i] 这个数字对应的图片显示到对应的位置
            // 根据数字找到对应的文件名字
            char name[128] = {0};
            sprintf(name, "%d.bmp", num[j][i]); // 格式化打印数据到内存空间
            lcd_draw_bmp(name, 190 + i * 105, 30 + j * 105);
        }
    }
}

// 处理上划操作(返回 1 表示出现了移动/合并，返回 0 表示没有出现合并移动)
int move_up()
{
	for (int j=0;j<4;j++){
		
		for(int i=0;i<4;i++){
			for (int k = i+1;k<4;k++){
				if(num[k][j] == 0){
					continue;
				}else{
					if(num[i][j] == 0){  //
						num[i][j] = num[k][j];
						num[k][j] = 0;
					}else{
						if(num[i][j] != num[k][j]){
							break;
						}else{
							num[i][j] *= 2;
							num[k][j] = 0;
						}
					}

				}
			}
		}
	}
    
    return 1;
}

// 处理下划操作
int move_down()
{
	for (int j=0;j<4;j++){
		
		for(int i=3;i>=0;i--){
			for (int k = i-1;k>=0;k--){
				if(num[k][j] == 0){
					continue;
				}else{
					if(num[i][j] == 0){
						num[i][j] = num[k][j];
						num[k][j] = 0;
					}else{
						if(num[i][j] != num[k][j]){
							break;
						}else{
							num[i][j] *= 2;
							num[k][j] = 0;
						}
					}

				}
			}
		}
	}
    
    return 1;
}

// 处理左划操作
int move_left()
{
    for (int j=0;j<4;j++){
		
		for(int i=0;i<4;i++){
			for (int k = j+1;k<4;k++){
				if(num[i][k] == 0){
					continue;
				}else{
					if(num[i][j] == 0){
						num[i][j] = num[i][k];
						num[i][k] = 0;
					}else{
						if(num[i][j] != num[i][k]){
							break;
						}else{
							num[i][j] *= 2;
							num[i][k] = 0;
						}
					}

				}
			}
		}
	}
    
    return 1;
}

// 处理右划操作
int move_right()
{
       for (int j=0;j<4;j++){
		
		for(int i=3;i>=0;i--){
			for (int k = j-1;k>=0;k--){
				if(num[i][k] == 0){
					continue;
				}else{
					if(num[i][j] == 0){
						num[i][j] = num[i][k];
						num[i][k] = 0;
					}else{
						if(num[i][j] != num[i][k]){
							break;
						}else{
							num[i][j] *= 2;
							num[i][k] = 0;
						}
					}

				}
			}
		}
	}
    
    return 1;
}

// 判断当前游戏是否失败（返回 0 就表示可以继续游戏，返回 1 就表示游戏结束了）
int checkover()
{
    // 检查是否有相邻的相同数字  //i 行 j 列
    for (int j = 0; j < 4; j++) {
        for (int i = 0; i < 4; i++) {
            // 检查左右：当当前行不是最后一行时，检查当前元素和右侧相邻元素是否相等
            if (j < 3 && num[i][j] == num[i][j + 1]) {
                // 若有相邻元素相等，则游戏可继续，返回 0
                return 0;
            }
            // 检查上下：当当前列不是最后一列时，检查当前元素和下方相邻元素是否相等
            if (j < 3 && num[j][i] == num[j + 1][i]) {
                // 若有相邻元素相等，则游戏可继续，返回 0
                return 0;
            }
            // 检查是否存在空白格子，如果存在，游戏可继续，返回 0
            if (num[i][j] == 0) {
                return 0;
            }
        }
    }
    // 检查是否所有格子都填满且没有相邻相同数字，如果都填满且没有相邻相同数字，则游戏结束，返回 1
    return 1;
}

int main()
{
    // 初始化屏幕
    lcd_init();
    // 刷屏
    lcd_clear(0x00FFFFFF);
    // 2048 小游戏的逻辑
    // 设置当前这一局游戏的随机数种子
    srand(time(NULL));

    // 游戏最开始的时候，会在矩阵中任意空白位置出现 2 个任意的 2 或者 4
    add_num();
    add_num();

    // 把当前的数组"画"到屏幕上
    draw_2048();

    // 游戏进入循环(接收用户的滑动方向)
    while (1) {
        // 不断接受用户的方向输入(上下左右)
        int r = get_touch();
        int flag = 0;  // 默认是没有出现滑动或者合并
        if (r == 10) // 向上
        {
            flag = move_up();
        } else if (r == 11) // 向下
        {
            flag = move_down();
        } else if (r == 12) // 向左
        {
            flag = move_left();
        } else if (r == 13) // 向右
        {
            flag = move_right();
        }

        // 如果在滑动的过程中，出现了合并或者移动，就会在随机空白位置出现一个随机的 2 或者 4
        if (flag == 1) {
            add_num();
        }
        // 把移动之后的界面画出来
        draw_2048();

        // 判断游戏是否失败
        if (checkover()) {
			sleep(2);
			lcd_clear(0x00FFFFFF);
			lcd_draw_bmp("game_over.bmp",0, 0);
			sleep(2);
			lcd_clear(0x00FFFFFF);
			// 在指定的位置，显示一个指定大小，指定颜色的矩形
			lcd_draw_rect(0, 0, 100, 100,0x00FF0000);
			int t=get_touch();
			if(t==1)
				// 重新开始游戏，需要重新初始化相关变量
				{
                srand(time(NULL));
                for (int i = 0; i < 4; i++) {
                    for (int j = 0; j < 4; j++) {
                        num[i][j] = 0;
                    }
                }
                add_num();
                add_num();
                draw_2048();
                continue;
            } else {
                break;
            }
			
        }
    }



    // 关闭屏幕
    lcd_close();
    return 0;
}



// 初始化屏幕
void lcd_init()
{
    // 1. 打开文件(open)
    fd = open("/dev/fb0", O_RDWR);
    if (fd == -1) {
        perror("open file error");
        return;
    }
    // 2. 建立映射关系(mmap)    
    plcd = (int *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (plcd == MAP_FAILED) {
        perror("mmap failed");
        return;
    }
}

// 关闭屏幕
void lcd_close()
{
    // 3. 解除映射关系(munmap)        
    munmap(plcd, 800 * 480 * 4);

    // 4. 关闭文件(close)
    close(fd);
}

// 可以在屏幕上指定的位置显示一个指定颜色的点
void lcd_draw_point(int x, int y, int color)
{
    if (x < 0 || x >= 800 || y < 0 || y >= 480) {
        printf("bro, you point out of the screen!\n");
        return;
    }
    *(plcd + 800 * y + x) = color;
}

// 刷屏，可以把屏幕刷成指定的颜色
void lcd_clear(int color)
{
    int x, y;
    for (y = 0; y < 480; y++)  // 屏幕有 480 行
    {
        for (x = 0; x < 800; x++)  // 每一行有 800 个点
        {
            lcd_draw_point(x, y, color);
        }
    }
}


// 在指定的位置，显示一个指定大小，指定颜色的矩形
void lcd_draw_rect(int x0, int y0, int w, int h, int color)
{
    int x, y;
    for (y = y0; y < y0 + h; y++) {
        for (x = x0; x < x0 + w; x++) {
            lcd_draw_point(x, y, color);
        }
    }
}


// 能做把指定的图片显示到指定的坐标(x0,y0)上
void lcd_draw_bmp(char *name, int x0, int y0)
{
    // a. 打开指定的图片文件
    int fd_bmp = open(name, O_RDWR);
    if (fd_bmp == -1) {
        printf("open bmp error!,请上传图片!!!!!!\n");
        return;
    }

    // 从 54 个字节中读取图片的宽度/高度和位深
    int w, h;
    lseek(fd_bmp, 0x12, SEEK_SET);  // 基于开头偏移 12 个字节
    read(fd_bmp, &w, 4);        // 读取会改变光标位置
    read(fd_bmp, &h, 4);

    short dep = 0;
    lseek(fd_bmp, 0x1C, SEEK_SET);  // 基于开头偏移 12 个字节
    read(fd_bmp, &dep, 2);

    lseek(fd_bmp, 0, SEEK_SET);  // 重新把光标偏移到文件开头

    // b. 读取文件中的所有像素信息
    char buf[800 * 480 * 4 + 54] = {0};
    read(fd_bmp, buf, 800 * 480 * 4 + 54);   // 能读取多少，就会读取多少

    // c. 关闭图片
    close(fd_bmp);

    // d. 解析 BMP 图片的每一个像素点颜色

    int x, y;
    char a = 0, r, g, b;
    int p = 54;
    for (y = h - 1; y >= 0; y--)    // w*h   当前图片有 h 行
    {
        for (x = 0; x < w; x++)    // w*h   当前图片每一行有 w 个点
        {
            // 解析并且显示每一个点
            b = buf[p++];
            g = buf[p++];
            r = buf[p++];
            if (dep == 32) {
                a = buf[p++];
            }

            int color = a << 24 | r << 16 | g << 8 | b;
            lcd_draw_point(x + x0, y + y0, color);
        }
        // 图片一行大小不是 4 的倍数，需要去除填充字节
        if (w * (dep / 8) % 4!= 0)  // 一行不是 4 的倍数
        {
            p = p + 4 - w * (dep / 8) % 4;
        }
    }
}



// 触摸屏操作函数
// 返回值表示点击了不同的位置/结果
int get_touch()
{
    // 1. 打开触摸屏设备(open)
    int fd_touch = open("/dev/input/event0", O_RDWR);
    if (fd_touch == -1) {
        perror("open event0 failed");
        return -1;
    }
    // 2. 读取输入事件(read)
    struct input_event  ev; // 定义一个结构体变量，存储将要读取到的数据
    int x0 = -1, y0 = -1;    // 用来记录起点坐标
    int x, y;             // 用来记录终点坐标
    while (1) {
        read(fd_touch, &ev, sizeof(ev));
        // 3. 解析输入事件
        if (ev.type == EV_ABS)  // 是绝对坐标事件
        {
            if (ev.code == ABS_X)  // 当前是 X 轴事件
            {
                if (x0 == -1) {
                    x0 = ev.value;
                }
                x = ev.value;
            } else if (ev.code == ABS_Y) {
                if (y0 == -1) {
                    y0 = ev.value;
                }
                y = ev.value;
            }
        }
        // 当手指离开屏幕的时候，结束循环
        if (ev.type == EV_KEY && ev.code == BTN_TOUCH && ev.value == 0) {
            break;
        }
    }

    // 4. 关闭文件(close)
    close(fd_touch);

    // 根据两个点之间的距离，判断是滑动操作还是点击操作
    if ((x - x0) * (x - x0) + (y - y0) * (y - y0) <= 10 * 10)  // 两个点之间的距离少于 10 个像素点，判断为点击事件
    {
        x = x / 1.28;
        y = y / 1.25;

        printf("x = %d,y = %d\n", x, y);

        // 点落在了指定的区域
        if (x >= 0 && x <= 100 && y >= 0 && y <= 100) {
            return 1;
        } else if (x >= 700 && x <= 800 && y >= 400 && y <= 480) {
            return 2;
        } else {
            return 3;
        }
    } else   //滑动事件
	{
		if(y<y0 && abs(y-y0) > abs(x-x0))
		{
			return 10; //表示上划操作
		}else if(y>y0 && abs(y-y0) > abs(x-x0))
		{
			return 11; //表示下划操作
		}else if(x<x0 && abs(y-y0) < abs(x-x0))
		{
			return 12; //表示左划操作
		}else if(x>x0 && abs(y-y0) < abs(x-x0))
		{
			return 13; //表示右划操作
		}else 
		{
			return 14; //未知方向
		}
	}

	
}


	





